package loppy.dynamics 
{
  import loppy.loppy_internal;
  import loppy.math.Vec2;
  import loppy.utils.InList;
  import loppy.utils.InListIterator;
  import loppy.utils.InListNode;
  
  public class Body extends InListNode
  { 
    use namespace loppy_internal;
    
    public static const STATIC   :uint = 0x1;
    public static const DYNAMIC  :uint = 0x2;
    public static const KINEMATIC:uint = 0x4;
    
    private static var idCounter:uint = 0;
    loppy_internal var id  :uint = 0;
    loppy_internal var type:uint = 0;
    
    loppy_internal var shapes:InList = new InList();
    loppy_internal var parent:World = null;
    
    loppy_internal var contactEdges:InList = new InList();
    
    loppy_internal var transformDirty:Boolean = true;
    
    loppy_internal var position:Vec2 = new Vec2();
    public function getPosition():Vec2 { return position.clone(); }
    public function setPosition(value:Vec2):Body
    {
      position.copy(value);
      transformDirty = true;
      return this;
    }
    
    loppy_internal var velocity:Vec2 = new Vec2(0.0, 0.0);
    public function getVelocity():Vec2 { return velocity.clone(); }
    public function setVelocity(value:Vec2):Body
    {
      velocity.copy(value);
      transformDirty = true;
      return this;
    }
    
    loppy_internal var rotation:Number = 0.0;
    public function getRotation():Number { return rotation; }
    public function setRotation(value:Number):Body
    {
      rotation = value;
      transformDirty = true;
      return this;
    }
    
    loppy_internal var omega:Number = 0.0;
    public function getOmega():Number { return omega; }
    public function setOmega(value:Number):Body
    {
      omega = value;
      return this;
    }
    
    loppy_internal var mass           :Number  = 0.0;
    loppy_internal var inverseMass    :Number  = 0.0;
    loppy_internal var intertia       :Number  = 0.0;
    loppy_internal var inverseIntertia:Number  = 0.0;
    loppy_internal var massDirty      :Boolean = false;
    
    loppy_internal var userData:* = null;
    public function getUserData():* { return userData; }
    public function setUserData(data:*):Body
    {
      userData = data;
      return this;
    }
    
    public function Body(type:uint = DYNAMIC) 
    {
      this.type = type;
    }
    
    public function add(shape:Shape):Body
    {
      if (shape.parent) throw new Error(shape + " already added to " + shape.parent);
      
      shapes.add(shape);
      shape.parent = this;
      
      //add shape to broadphase
      if (parent)
      {
        parent.broadphase.add(shape.proxy);
      }
      
      massDirty = true;
      
      //TODO: optimize
      updateShapeTransforms();
      
      return this;
    }
    
    public function remove(shape:Shape):Body
    {
      if (shape.parent != this) throw new Error(shape + " does not belong to " + this);
      
      shapes.remove(shape);
      shape.parent = null;
      
      //remove shape from broadphase
      if (parent)
      {
        parent.broadphase.remove(shape.proxy);
      }
      
      massDirty = true;
      
      return this;
    }
    
    public function applyImpulse(impulse:Vec2, position:Vec2 = null):Body
    {
      if (!position) position = new Vec2(0.0, 0.0);
      velocity.x += impulse.x * inverseMass;
      velocity.y += impulse.y * inverseMass;
      omega      += position.cross(impulse) * inverseIntertia;
      return this;
    }
    
    public function integrate(dt:Number):void
    {
      switch (type)
      {
        case DYNAMIC:
        case KINEMATIC:
        {
          position.x += velocity.x * dt;
          position.y += velocity.y * dt;
          rotation   += omega      * dt;
          
          if (transformDirty)
          {
            updateShapeTransforms();
            transformDirty = false;
          }
          break;
        }
        
        case STATIC:
        {
          velocity.set(0.0, 0.0);
          omega = 0.0;
          
          if (transformDirty)
          {
            updateShapeTransforms();
            transformDirty = false;
          }
          break;
        }
      }
    }
    
    loppy_internal function updateShapeTransforms():void
    {
      if (!shapes.size()) return;
      
      for (var shapeIter:InListIterator = shapes.begin(); !shapeIter.isEnd(); shapeIter.next())
      {
        var shape:Shape = Shape(shapeIter.data());
        shape.updateTransform();
        shape.updateProxy();
      }
    }
    
    loppy_internal function updateMass():void
    {
      for (var shapeIter:InListIterator = shapes.begin(); !shapeIter.isEnd(); shapeIter.next())
      {
        var shape:Shape = shapeIter.data();
        
        //TODO: optimize (shouldn't have to call Shape.updateMass every time)
        shape.updateMass();
        
        mass     += shape.mass;
        intertia += shape.intertia * shape.localPosition.lengthSQ();
      }
      inverseMass     = 1.0 / mass;
      inverseIntertia = 1.0 / intertia;
      
      massDirty = false;
    }
    
    public function clear():Body
    {
      var shapeIter:InListIterator = shapes.begin();
      while (shapeIter.isEnd())
      {
        var shape:Shape = shapeIter.data();
        shapes.remove(shape);
        shape.parent = null;
        
        //remove shape from broadphase
        if (parent)
        {
          parent.broadphase.remove(shape.proxy);
        }
        shapeIter.next();
      }
      shapes.clear();
      
      massDirty = true;
      
      return this;
    }
    
    public function toString():String
    {
      return "[Body id=" + id + "]";
    }
  }
}
